/*
 Copyright (C) 2010 Kristian Duske

 This file is part of TrenchBroom.

 TrenchBroom is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 TrenchBroom is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with TrenchBroom. If not, see <http://www.gnu.org/licenses/>.
 */

#include "Node.h"

#include "Macros.h"
#include "mdl/EntityProperties.h"
#include "mdl/Issue.h"
#include "mdl/Validator.h"

#include "kd/const_overload.h"
#include "kd/contracts.h"
#include "kd/range_utils.h"
#include "kd/ranges/as_rvalue_view.h"
#include "kd/ranges/to.h"
#include "kd/reflection_impl.h"
#include "kd/vector_utils.h"

#include <algorithm>
#include <iterator>
#include <ranges>
#include <string>
#include <vector>

namespace tb::mdl
{

kdl_reflect_impl(NodePath);

Node::Node() = default;

Node::~Node()
{
  clearChildren();
}

const std::string& Node::name() const
{
  return doGetName();
}

NodePath Node::pathFrom(const Node& ancestor) const
{
  auto result = NodePath{};

  auto* parent = m_parent;
  auto* child = this;
  while (parent && child != &ancestor)
  {
    const auto index = kdl::index_of(parent->children(), child);

    result.indices.push_back(*index);
    child = parent;
    parent = parent->m_parent;
  }

  contract_assert(child == &ancestor);

  std::ranges::reverse(result.indices);
  return result;
}

Node* Node::resolvePath(const NodePath& path)
{
  return KDL_CONST_OVERLOAD(resolvePath(path));
}

const Node* Node::resolvePath(const NodePath& path) const
{
  const auto* node = this;
  for (const auto index : path.indices)
  {
    if (index >= node->childCount())
    {
      return nullptr;
    }

    node = node->children()[index];
  }
  return node;
}

const vm::bbox3d& Node::logicalBounds() const
{
  return doGetLogicalBounds();
}

const vm::bbox3d& Node::physicalBounds() const
{
  return doGetPhysicalBounds();
}

double Node::projectedArea(const vm::axis::type axis) const
{
  return doGetProjectedArea(axis);
}

Node* Node::clone(const vm::bbox3d& worldBounds) const
{
  return doClone(worldBounds);
}

Node* Node::cloneRecursively(const vm::bbox3d& worldBounds) const
{
  return doCloneRecursively(worldBounds);
}

void Node::cloneAttributes(Node& node) const
{
  node.setVisibilityState(m_visibilityState);
  node.setLockState(m_lockState);
}

std::vector<Node*> Node::clone(
  const vm::bbox3d& worldBounds, const std::vector<Node*>& nodes)
{
  auto clones = std::vector<Node*>{};
  clones.reserve(nodes.size());
  clone(worldBounds, std::begin(nodes), std::end(nodes), std::back_inserter(clones));
  return clones;
}

std::vector<Node*> Node::cloneRecursively(
  const vm::bbox3d& worldBounds, const std::vector<Node*>& nodes)
{
  auto clones = std::vector<Node*>{};
  clones.reserve(nodes.size());
  cloneRecursively(
    worldBounds, std::begin(nodes), std::end(nodes), std::back_inserter(clones));
  return clones;
}

size_t Node::depth() const
{
  return m_parent ? m_parent->depth() + 1 : 0;
}

Node* Node::parent() const
{
  return m_parent;
}

bool Node::isAncestorOf(const Node* node) const
{
  return node->isDescendantOf(this);
}

bool Node::isAncestorOf(const std::vector<Node*>& nodes) const
{
  return std::ranges::any_of(
    nodes, [this](const Node* node) { return isAncestorOf(node); });
}

bool Node::isDescendantOf(const Node* node) const
{
  Node* parent = m_parent;
  while (parent)
  {
    if (parent == node)
    {
      return true;
    }
    parent = parent->parent();
  }
  return false;
}

bool Node::isDescendantOf(const std::vector<Node*>& nodes) const
{
  return any_of(std::begin(nodes), std::end(nodes), [this](const Node* node) {
    return isDescendantOf(node);
  });
}

std::vector<Node*> Node::findDescendants(const std::vector<Node*>& nodes) const
{
  auto result = std::vector<Node*>{};
  for (auto* node : nodes)
  {
    if (node->isDescendantOf(this))
    {
      result.push_back(node);
    }
  }
  return result;
}

bool Node::removeIfEmpty() const
{
  return doRemoveIfEmpty();
}

bool Node::hasChildren() const
{
  return !m_children.empty();
}

size_t Node::childCount() const
{
  return m_children.size();
}

const std::vector<Node*>& Node::children() const
{
  return m_children;
}

size_t Node::descendantCount() const
{
  return m_descendantCount;
}

size_t Node::familySize() const
{
  return m_descendantCount + 1;
}

bool Node::shouldAddToSpacialIndex() const
{
  return doShouldAddToSpacialIndex();
}

void Node::addChildren(const std::vector<Node*>& children)
{
  addChildren(std::begin(children), std::end(children), children.size());
}

Node& Node::addChild(Node* child)
{
  doAddChild(child);
  incDescendantCount(child->descendantCount() + 1u);
  incChildSelectionCount(child->selected() ? 1u : 0u);
  incDescendantSelectionCount(child->descendantSelectionCount());
  return *child;
}

std::vector<std::unique_ptr<Node>> Node::replaceChildren(
  std::vector<std::unique_ptr<Node>> newChildren)
{
  // nodeWillChange();

  for (auto* child : m_children)
  {
    contract_assert(child != nullptr);
    contract_assert(child->parent() == this);
    contract_assert(canRemoveChild(child));

    childWillBeRemoved(child);
    child->setParent(nullptr);
  }

  auto oldChildren =
    m_children
    | std::views::transform([](auto* child) { return std::unique_ptr<Node>(child); })
    | kdl::ranges::to<std::vector>();
  m_children.clear();

  for (auto& child : oldChildren)
  {
    childWasRemoved(child.get());
  }

  decDescendantCount(descendantCount());
  addChildren(
    newChildren | kdl::views::as_rvalue
    | std::views::transform([](auto&& child) { return child.release(); })
    | kdl::ranges::to<std::vector>());

  // nodeDidChange();

  return oldChildren;
}

void Node::removeChild(Node* child)
{
  doRemoveChild(child);
  decDescendantCount(child->descendantCount() + 1u);
  decChildSelectionCount(child->selected() ? 1u : 0u);
  decDescendantSelectionCount(child->descendantSelectionCount());
}

bool Node::canAddChild(const Node* child) const
{
  return child != this && !isDescendantOf(child) && doCanAddChild(child);
}

bool Node::canRemoveChild(const Node* child) const
{
  return doCanRemoveChild(child);
}

void Node::doAddChild(Node* child)
{
  contract_pre(child != nullptr);
  contract_pre(child->parent() == nullptr);
  contract_pre(!kdl::vec_contains(m_children, child));
  contract_pre(canAddChild(child));

  childWillBeAdded(child);
  // nodeWillChange();
  m_children.push_back(child);
  child->setParent(this);
  childWasAdded(child);
  // nodeDidChange();
}

void Node::doRemoveChild(Node* child)
{
  contract_pre(child != nullptr);
  contract_pre(child->parent() == this);
  contract_pre(canRemoveChild(child));

  childWillBeRemoved(child);
  // nodeWillChange();
  child->setParent(nullptr);
  m_children = kdl::vec_erase(std::move(m_children), child);
  childWasRemoved(child);
  // nodeDidChange();
}

void Node::clearChildren()
{
  kdl::vec_clear_and_delete(m_children);
}

void Node::childWillBeAdded(Node* node)
{
  doChildWillBeAdded(node);
  descendantWillBeAdded(this, node, 1);
}

void Node::childWasAdded(Node* node)
{
  doChildWasAdded(node);
  descendantWasAdded(node, 1);
}

void Node::childWillBeRemoved(Node* node)
{
  doChildWillBeRemoved(node);
  descendantWillBeRemoved(node, 1);
}

void Node::childWasRemoved(Node* node)
{
  doChildWasRemoved(node);
  descendantWasRemoved(this, node, 1);
}

void Node::descendantWillBeAdded(Node* newParent, Node* node, const size_t depth)
{
  doDescendantWillBeAdded(newParent, node, depth);
  if (m_parent)
  {
    m_parent->descendantWillBeAdded(newParent, node, depth + 1);
  }
}

void Node::descendantWasAdded(Node* node, const size_t depth)
{
  doDescendantWasAdded(node, depth);
  if (m_parent)
  {
    m_parent->descendantWasAdded(node, depth + 1);
  }
  invalidateIssues();
}

void Node::descendantWillBeRemoved(Node* node, const size_t depth)
{
  doDescendantWillBeRemoved(node, depth);
  if (m_parent)
  {
    m_parent->descendantWillBeRemoved(node, depth + 1);
  }
}

void Node::descendantWasRemoved(Node* oldParent, Node* node, const size_t depth)
{
  doDescendantWasRemoved(oldParent, node, depth);
  if (m_parent)
  {
    m_parent->descendantWasRemoved(oldParent, node, depth + 1);
  }
  invalidateIssues();
}

void Node::incDescendantCount(const size_t delta)
{
  if (delta != 0)
  {
    m_descendantCount += delta;
    if (m_parent)
    {
      m_parent->incDescendantCount(delta);
    }
  }
}

void Node::decDescendantCount(const size_t delta)
{
  contract_pre(m_descendantCount >= delta);

  if (delta != 0)
  {
    m_descendantCount -= delta;
    if (m_parent)
    {
      m_parent->decDescendantCount(delta);
    }
  }
}

void Node::setParent(Node* parent)
{
  contract_pre((m_parent == nullptr) != (parent == nullptr));
  contract_pre(parent != this);

  if (parent != m_parent)
  {
    parentWillChange();
    m_parent = parent;
    parentDidChange();
  }
}

void Node::parentWillChange()
{
  doParentWillChange();
  ancestorWillChange();
}

void Node::parentDidChange()
{
  doParentDidChange();
  ancestorDidChange();
}

void Node::ancestorWillChange()
{
  doAncestorWillChange();
  for (auto* child : m_children)
  {
    child->ancestorWillChange();
  }
  invalidateIssues();
}

void Node::ancestorDidChange()
{
  doAncestorDidChange();
  for (auto* child : m_children)
  {
    child->ancestorDidChange();
  }
  invalidateIssues();
}

void Node::nodeWillChange()
{
  if (m_parent)
  {
    m_parent->childWillChange(this);
  }
  invalidateIssues();
}

void Node::nodeDidChange()
{
  if (m_parent)
  {
    m_parent->childDidChange(this);
  }
  invalidateIssues();
}

Node::NotifyNodeChange::NotifyNodeChange(Node& node)
  : m_node{node}
{
  m_node.nodeWillChange();
}

Node::NotifyNodeChange::~NotifyNodeChange()
{
  m_node.nodeDidChange();
}

Node::NotifyPhysicalBoundsChange::NotifyPhysicalBoundsChange(Node& node)
  : m_node{node}
{
}

Node::NotifyPhysicalBoundsChange::~NotifyPhysicalBoundsChange()
{
  m_node.nodePhysicalBoundsDidChange();
}

void Node::nodePhysicalBoundsDidChange()
{
  doNodePhysicalBoundsDidChange();
  if (m_parent)
  {
    m_parent->childPhysicalBoundsDidChange(this);
  }
}

void Node::childWillChange(Node* node)
{
  doChildWillChange(node);
  descendantWillChange(node);
}

void Node::childDidChange(Node* node)
{
  doChildDidChange(node);
  descendantDidChange(node);
}

void Node::descendantWillChange(Node* node)
{
  doDescendantWillChange(node);
  if (m_parent)
  {
    m_parent->descendantWillChange(node);
  }
  invalidateIssues();
}

void Node::descendantDidChange(Node* node)
{
  doDescendantDidChange(node);
  if (m_parent)
  {
    m_parent->descendantDidChange(node);
  }
  invalidateIssues();
}

void Node::childPhysicalBoundsDidChange(Node* node)
{
  nodePhysicalBoundsDidChange();
  doChildPhysicalBoundsDidChange();
  descendantPhysicalBoundsDidChange(node, 1);
}

void Node::descendantPhysicalBoundsDidChange(Node* node, const size_t depth)
{
  doDescendantPhysicalBoundsDidChange(node);
  if (m_parent)
  {
    m_parent->descendantPhysicalBoundsDidChange(node, depth + 1);
  }
}

bool Node::selected() const
{
  return m_selected;
}

void Node::select()
{
  if (selectable())
  {
    contract_assert(!m_selected);

    m_selected = true;
    if (m_parent)
    {
      m_parent->childWasSelected();
    }
  }
}

void Node::deselect()
{
  if (selectable())
  {
    contract_assert(m_selected);

    m_selected = false;
    if (m_parent)
    {
      m_parent->childWasDeselected();
    }
  }
}

bool Node::transitivelySelected() const
{
  return selected() || parentSelected();
}

bool Node::parentSelected() const
{
  return m_parent && (m_parent->selected() || m_parent->parentSelected());
}

bool Node::childSelected() const
{
  return m_childSelectionCount > 0;
}

size_t Node::childSelectionCount() const
{
  return m_childSelectionCount;
}

bool Node::descendantSelected() const
{
  return m_descendantSelectionCount > 0;
}

size_t Node::descendantSelectionCount() const
{
  return m_descendantSelectionCount;
}

void Node::childWasSelected()
{
  incChildSelectionCount(1);
}

void Node::childWasDeselected()
{
  decChildSelectionCount(1);
}

std::vector<Node*> Node::nodesRequiredForViewSelection()
{
  return std::vector<Node*>{this};
}

void Node::incChildSelectionCount(const size_t delta)
{
  if (delta != 0)
  {
    m_childSelectionCount += delta;
    incDescendantSelectionCount(delta);
  }
}

void Node::decChildSelectionCount(const size_t delta)
{
  contract_pre(m_childSelectionCount >= delta);

  if (delta != 0)
  {
    m_childSelectionCount -= delta;
    decDescendantSelectionCount(delta);
  }
}

void Node::incDescendantSelectionCount(const size_t delta)
{
  if (delta != 0)
  {
    m_descendantSelectionCount += delta;
    if (m_parent)
    {
      m_parent->incDescendantSelectionCount(delta);
    }
  }
}

void Node::decDescendantSelectionCount(const size_t delta)
{
  contract_pre(m_descendantSelectionCount >= delta);

  if (delta != 0)
  {
    m_descendantSelectionCount -= delta;
    if (m_parent)
    {
      m_parent->decDescendantSelectionCount(delta);
    }
  }
}

bool Node::selectable() const
{
  return doSelectable();
}

bool Node::visible() const
{
  switch (m_visibilityState)
  {
  case VisibilityState::Inherited:
    return !m_parent || m_parent->visible();
  case VisibilityState::Hidden:
    return false;
  case VisibilityState::Shown:
    return true;
    switchDefault();
  }
}

bool Node::shown() const
{
  return m_visibilityState == VisibilityState::Shown;
}

bool Node::hidden() const
{
  return m_visibilityState == VisibilityState::Hidden;
}

VisibilityState Node::visibilityState() const
{
  return m_visibilityState;
}

bool Node::setVisibilityState(const VisibilityState visibility)
{
  if (visibility != m_visibilityState)
  {
    m_visibilityState = visibility;
    return true;
  }
  return false;
}

bool Node::ensureVisible()
{
  return !visible() ? setVisibilityState(VisibilityState::Shown) : false;
}

bool Node::editable() const
{
  if (m_lockedByOtherSelection)
  {
    return false;
  }
  switch (m_lockState)
  {
  case LockState::Inherited:
    return !m_parent || m_parent->editable();
  case LockState::Locked:
    return false;
  case LockState::Unlocked:
    return true;
    switchDefault();
  }
}

bool Node::locked() const
{
  return !editable();
}

LockState Node::lockState() const
{
  return m_lockState;
}

bool Node::setLockState(const LockState lockState)
{
  if (lockState != m_lockState)
  {
    m_lockState = lockState;
    return true;
  }
  return false;
}

bool Node::lockedByOtherSelection() const
{
  return m_lockedByOtherSelection;
}

void Node::setLockedByOtherSelection(const bool lockedByOtherSelection)
{
  m_lockedByOtherSelection = lockedByOtherSelection;
}

void Node::pick(
  const EditorContext& editorContext, const vm::ray3d& ray, PickResult& pickResult)
{
  doPick(editorContext, ray, pickResult);
}

void Node::findNodesContaining(const vm::vec3d& point, std::vector<Node*>& result)
{
  doFindNodesContaining(point, result);
}

size_t Node::lineNumber() const
{
  return m_lineNumber;
}

void Node::setFilePosition(const size_t lineNumber, const size_t lineCount) const
{
  m_lineNumber = lineNumber;
  m_lineCount = lineCount;
}

bool Node::containsLine(const size_t lineNumber) const
{
  return lineNumber >= m_lineNumber && lineNumber < m_lineNumber + m_lineCount;
}

std::vector<const Issue*> Node::issues(const std::vector<const Validator*>& validators)
{
  validateIssues(validators);
  return m_issues | std::views::transform([](const auto& issue) {
           return const_cast<const Issue*>(issue.get());
         })
         | kdl::ranges::to<std::vector>();
}

bool Node::issueHidden(const IssueType type) const
{
  return (type & m_hiddenIssues) != 0;
}

void Node::setIssueHidden(const IssueType type, const bool hidden)
{
  if (hidden)
  {
    m_hiddenIssues |= type;
  }
  else
  {
    m_hiddenIssues &= ~type;
  }
}

void Node::validateIssues(const std::vector<const Validator*>& validators)
{
  if (!m_issuesValid)
  {
    for (const auto* validator : validators)
    {
      validator->validate(*this, m_issues);
    }
    m_issuesValid = true;
  }
}

void Node::invalidateIssues() const
{
  m_issues.clear();
  m_issuesValid = false;
}

const EntityPropertyConfig& Node::entityPropertyConfig() const
{
  return doGetEntityPropertyConfig();
}

Node* Node::doCloneRecursively(const vm::bbox3d& worldBounds) const
{
  auto* clone = Node::clone(worldBounds);
  clone->addChildren(Node::cloneRecursively(worldBounds, children()));
  return clone;
}

void Node::doChildWillBeAdded(Node* /* node */) {}
void Node::doChildWasAdded(Node* /* node */) {}
void Node::doChildWillBeRemoved(Node* /* node */) {}
void Node::doChildWasRemoved(Node* /* node */) {}

void Node::doDescendantWillBeAdded(
  Node* /* newParent */, Node* /* node */, const size_t /* depth */)
{
}
void Node::doDescendantWasAdded(Node* /* node */, const size_t /* depth */) {}
void Node::doDescendantWillBeRemoved(Node* /* node */, const size_t /* depth */) {}
void Node::doDescendantWasRemoved(
  Node* /* oldParent */, Node* /* node */, const size_t /* depth */)
{
}

void Node::doParentWillChange() {}
void Node::doParentDidChange() {}
void Node::doAncestorWillChange() {}
void Node::doAncestorDidChange() {}

void Node::doNodePhysicalBoundsDidChange() {}
void Node::doChildPhysicalBoundsDidChange() {}
void Node::doDescendantPhysicalBoundsDidChange(Node* /* node */) {}

void Node::doChildWillChange(Node* /* node */) {}
void Node::doChildDidChange(Node* /* node */) {}
void Node::doDescendantWillChange(Node* /* node */) {}
void Node::doDescendantDidChange(Node* /* node */) {}

const EntityPropertyConfig& Node::doGetEntityPropertyConfig() const
{
  if (m_parent)
  {
    return m_parent->entityPropertyConfig();
  }

  static const auto defaultConfig = EntityPropertyConfig{};
  return defaultConfig;
}

} // namespace tb::mdl
